#include "qpicturebox.h"

#include <QMatrix4x4>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QPainter>

// clang-format off
static const float vertices[] = {
    -1,  1,     0, 0,
     1,  1,     1, 0,
    -1, -1,     0, 1,
     1, -1,     1, 1,
};

static const char *vertexShaderSource =
    "#version 330 core\n"
    "layout(location=0) in vec2 position;\n"
    "layout(location=1) in vec2 texCoord;\n"
    "out vec2 TexCoord;\n"
    "uniform mat4 transform;\n"
    "void main() {\n"
    "   gl_Position = transform * vec4(position, 0.0, 1.0);\n"
    "   TexCoord = texCoord;\n"
    "}";

static const char *fragmentShaderSource =
    "#version 330 core\n"
    "in vec2 TexCoord;\n"
    "out vec4 FragColor;\n"
    "uniform sampler2D tex;\n"
    "void main() {\n"
    "   FragColor = texture(tex, TexCoord);\n"
    "}";

// clang-format on

QPictureBox::QPictureBox(QWidget *parent)
    : QOpenGLWidget(parent)
    , m_program(new QOpenGLShaderProgram(this))
    , m_vbo(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer))
    , m_vao(new QOpenGLVertexArrayObject(this))
{
    setCursor(Qt::BlankCursor);
}

void QPictureBox::setImage(const QImage &image)
{
    m_image = image;

    if (!isValid())
        return;

    resetTexture();
    update();
}

void QPictureBox::setScaleType(QPictureBox::ScaleType x)
{
    m_scaleType = x;
    update();
}

void QPictureBox::setBackGround(QColor color)
{
    m_background = color;
    update();
}

void QPictureBox::setFilterType(FilterType filter)
{
    m_filterType = filter;

    if (!isValid())
        return;

    makeCurrent();
    if (m_texture && m_texture->isCreated()) {
        m_texture->setMinificationFilter(QOpenGLTexture::Filter(filter));
        m_texture->setMagnificationFilter(QOpenGLTexture::Filter(filter));
    }
    doneCurrent();

    update();
}

uint32_t QPictureBox::fps()
{
    return m_currentFps;
}

void QPictureBox::initializeGL()
{
    initializeOpenGLFunctions();

    glClearColor((GLfloat)m_background.red() / 255.0, (GLfloat)m_background.green() / 255.0, (GLfloat)m_background.blue() / 255.0, (GLfloat)m_background.alpha() / 255.0);

    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    m_program->link();
    m_program->bind();

    m_vao->create();
    m_vao->bind();

    m_vbo->create();
    m_vbo->bind();
    m_vbo->allocate(vertices, sizeof(vertices));

    m_program->enableAttributeArray(0);
    m_program->setAttributeBuffer(0, GL_FLOAT, 0, 2, 4 * sizeof(GLfloat));
    m_program->enableAttributeArray(1);
    m_program->setAttributeBuffer(1, GL_FLOAT, 2 * sizeof(GLfloat), 2, 4 * sizeof(GLfloat));

    if (!m_texture)
        resetTexture();
}

void QPictureBox::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void QPictureBox::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT);

    if (!m_texture || !m_texture->isCreated())
        return;

    QMatrix4x4 matrix;
    float w_ratio = float(width()) / m_image.width();
    float h_ratio = float(height()) / m_image.height();
    switch (m_scaleType) {
    case fitXY:
        matrix.scale(1.0f, 1.0f);
        break;
    case fitCenter: {
        float scale = qMin(w_ratio, h_ratio);
        float sx = m_image.width() * scale / width();
        float sy = m_image.height() * scale / height();
        matrix.scale(sx, sy);
        break;
    }
    case CenterCrop: {
        float scale = qMax(w_ratio, h_ratio);
        float sx = m_image.width() * scale / width();
        float sy = m_image.height() * scale / height();
        matrix.translate(-0.5f * (sx - 1.0f), -0.5f * (sy - 1.0f));
        matrix.scale(sx, sy);
        break;
    }
    default:
        break;
    }

    m_program->bind();
    m_texture->bind();
    m_vao->bind();
    m_program->setUniformValue("transform", matrix);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    ++m_frameCount;
    if (!m_fpsTimer.isValid()) {
        m_fpsTimer.start();
    } else if (m_fpsTimer.elapsed() >= 1000) {
        m_currentFps = m_frameCount * 1000.0f / m_fpsTimer.elapsed();
        fpsChanged(m_currentFps);
        m_frameCount = 0;
        m_fpsTimer.restart();
    }
}

void QPictureBox::resetTexture()
{
    if (m_image.isNull())
        return;

    makeCurrent();
    if (m_texture) {
        delete m_texture;
        m_texture = nullptr;
    }
    m_texture = new QOpenGLTexture(m_image);
    m_texture->setMinificationFilter(QOpenGLTexture::Filter(m_filterType));
    m_texture->setMagnificationFilter(QOpenGLTexture::Filter(m_filterType));
    m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
    doneCurrent();
}